////////////////////////////////////////////////////////////////////////////////
// Copyright 2010 Michael Schmalle - Teoti Graphix, LLC
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0 
// 
// Unless required by applicable law or agreed to in writing, software 
// distributed under the License is distributed on an "AS IS" BASIS, 
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and 
// limitations under the License
// 
// Author: Michael Schmalle, Principal Architect
// mschmalle at teotigraphix dot com
////////////////////////////////////////////////////////////////////////////////

package org.teotigraphix.swizplugin.services
{

import flash.events.EventDispatcher;
import flash.system.ApplicationDomain;
import flash.utils.Dictionary;

import mx.collections.ArrayCollection;
import mx.core.FlexGlobals;
import mx.events.ModuleEvent;
import mx.modules.IModuleInfo;
import mx.modules.ModuleManager;

import org.swizframework.core.IBeanFactory;
import org.swizframework.core.ISwiz;
import org.swizframework.core.ISwizAware;
import org.swizframework.utils.logging.SwizLogger;
import org.teotigraphix.swizplugin.api.IExecutableRequest;
import org.teotigraphix.swizplugin.api.IPlugin;
import org.teotigraphix.swizplugin.bundles.Bundle;
import org.teotigraphix.swizplugin.bundles.BundleData;
import org.teotigraphix.swizplugin.bundles.BundleServiceAdaptor;
import org.teotigraphix.swizplugin.bundles.IBundle;
import org.teotigraphix.swizplugin.bundles.IBundleActivator;
import org.teotigraphix.swizplugin.bundles.IBundleServiceAdaptor;
import org.teotigraphix.swizplugin.bundles.ISwizBundle;
import org.teotigraphix.swizplugin.bundles.ISwizContext;
import org.teotigraphix.swizplugin.core.ExecutableRequest;
import org.teotigraphix.swizplugin.events.BundleServiceEvent;

/**
 * The <strong>BundleService</strong> is the default implementation 
 * of <code>IBundleService</code>.
 * 
 * <p>Listeners can be added to receive the <code>BundleServiceEvent</code> 
 * events.</p>
 * 
 * @author Michael Schmalle
 * @copyright Teoti Graphix, LLC
 * @productversion 1.0
 * @see org.teotigraphix.swizplugin.events.BundleServiceEvent
 */
public class BundleService extends EventDispatcher 
	implements IBundleService, ISwizAware
{
	//--------------------------------------------------------------------------
	//
	//  Public :: Variables
	//
	//--------------------------------------------------------------------------
	
	[Inject]
	public var beanService:IBeanService;
	
	//--------------------------------------------------------------------------
	//
	//  Private :: Variables
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	private var _map:Dictionary = new Dictionary();
	
	/**
	 * @private
	 */
	private var _collection:ArrayCollection = new ArrayCollection();
	
	/**
	 * @private
	 */
	private var _bundleDatas:Array; // of IBundleData
	
	/**
	 * @private
	 */
	private var pendingModules:Dictionary = new Dictionary();
	
	//--------------------------------------------------------------------------
	//
	//  Private Class :: Variables
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	private static var bundleID:int = -1;
	
	/**
	 * @private
	 */
	private static function nextID():int
	{
		return ++bundleID;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Protected :: Variables
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	private var logger:SwizLogger = SwizLogger.getLogger(this);
	
	//--------------------------------------------------------------------------
	//
	//  Protected :: Properties
	//
	//--------------------------------------------------------------------------
	
	//----------------------------------
	//  adaptor
	//----------------------------------
	
	/**
	 * @private
	 */
	private var _adaptor:IBundleServiceAdaptor;
	
	/**
	 * @private
	 */
	protected function get adaptor():IBundleServiceAdaptor
	{
		return _adaptor;
	}
	
	//--------------------------------------------------------------------------
	//
	//  IBundleService API :: Properties
	//
	//--------------------------------------------------------------------------
	
	//----------------------------------
	//  active
	//----------------------------------
	
	/**
	 * @private
	 */
	private var _active:Boolean = false;
	
	/**
	 * @copy org.teotigraphix.swizplugin.services.IBundleService#active
	 */
	public function get active():Boolean
	{
		return _active;
	}
	
	/**
	 * @private
	 */	
	protected function setActive(value:Boolean):void
	{
		_active = value;
		
		logger.info("BundleService now active");
	}
	
	//--------------------------------------------------------------------------
	//
	//  ISwizAware API :: Properties
	//
	//--------------------------------------------------------------------------
	
	//----------------------------------
	//  swiz
	//----------------------------------
	
	/**
	 * @private
	 */
	private var _swiz:ISwiz;
	
	/**
	 * The root <code>ISwiz</code> instance.
	 */
	public function get swiz():ISwiz
	{
		return _swiz;
	}
	
	/**
	 * @private
	 */	
	public function set swiz(value:ISwiz):void
	{
		_swiz = value;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 * The serivce is instatiated in a BeanProvider.
	 */
	public function BundleService()
	{
		super();
	}
	
	//--------------------------------------------------------------------------
	//
	//  IBundleService API :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @copy org.teotigraphix.swizplugin.services.IBundleService#start()
	 */
	public function start():void
	{
		initializeBundles();
		
		startBundles();
	}
	
	/**
	 * @copy org.teotigraphix.swizplugin.services.IBundleService#addBundle()
	 */
	public function addBundle(bundle:IBundle):void
	{
		var symbolicName:String = bundle.symbolicName;
		
		if (_map[symbolicName])
		{
			logger.warn("BundleService.addBundle() '{0}' already exists in BundleService", symbolicName);
			return;
		}
		
		if (bundle is Bundle)
			Bundle(bundle).bundleID = nextID();
		
		_map[symbolicName] = bundle;
		_collection.addItem(bundle);
		
		logger.info("BundleService added '{0}' bundle, with id '{1}'", symbolicName, bundle.bundleID);
		
		dispatchEvent(new BundleServiceEvent(
			BundleServiceEvent.BUNDLE_ADD, bundle));
	}
	
	/**
	 * @copy org.teotigraphix.swizplugin.services.IBundleService#removeBundle()
	 */
	public function removeBundle(bundle:IBundle):void
	{
		var symbolicName:String = bundle.symbolicName;
		
		if (!_map[symbolicName])
		{
			logger.warn("BundleService.removeBundle() '{0}' does note exist in BundleService", symbolicName);
			return;
		}
		
		_map[symbolicName] = null;
		delete _map[symbolicName];
		
		_collection.removeItemAt(_collection.getItemIndex(bundle));
		
		logger.info("BundleService removed '{0}' bundle", symbolicName);
		
		dispatchEvent(new BundleServiceEvent(
			BundleServiceEvent.BUNDLE_REMOVE, bundle));
	}
	
	/**
	 * @copy org.teotigraphix.swizplugin.services.IBundleService#getBundle()
	 */
	public function getBundle(bundleID:int):IBundle
	{
		for each (var bundle:IBundle in _collection) 
		{
			if (bundle.bundleID == bundleID)
				return bundle;
		}
		
		return null;
	}
	
	/**
	 * @copy org.teotigraphix.swizplugin.services.IBundleService#getBundleByName()
	 */
	public function getBundleByName(symbolicName:String):IBundle
	{
		return _map[symbolicName];
	}
	
	/**
	 * @copy org.teotigraphix.swizplugin.services.IBundleService#getBundles()
	 */
	public function getBundles():Array
	{
		return _collection.toArray();
	}
	
	/**
	 * @copy org.teotigraphix.swizplugin.services.IBundleService#loadBundle()
	 */
	public function loadBundle(url:String):void
	{
		logger.info("Loading bundle at '{0}' url", url);
		
		var info:IModuleInfo = ModuleManager.getModule(url);
		
		addModule(info);
		
		if (!info.loaded && !info.ready)
		{
			info.load(ApplicationDomain.currentDomain);
		}
		else
		{
			delete pendingModules[info];
			logger.warn("Bundle at '{0}' url already loaded", url);
		}
	}
	
	/**
	 * @copy org.teotigraphix.swizplugin.api.IBundle#loadClass()
	 */
	public function loadClass(bundle:IBundle, className:String):IExecutableRequest
	{
		var type:Class = null;
		
		var context:ISwizContext = bundle.context as ISwizContext;
		
		try {
			type = Class(context.getDefinition(className));
		}
		catch (e:ReferenceError)
		{
			logger.error("BundleService.loadClass() - " + e.message);
			return null;
		}
		
		var token:IExecutableRequest = createExecutableRequest();
		
		FlexGlobals.topLevelApplication.callLater(instanceReady, [token, type]);
		
		return token;
	}
	
	/**
	 * @copy org.teotigraphix.swizplugin.services.IBundleService#createExecutableRequest()
	 */
	public function createExecutableRequest():IExecutableRequest
	{
		return new ExecutableRequest();
	}
	
	//--------------------------------------------------------------------------
	//
	//  Protected :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	protected function initializeBundles():void
	{
		// creates new BundleStorage
		_adaptor = createAdaptor();
		// sets ref to us
		_adaptor.initialize(this, swiz.beanFactory);
		// sets the adapator on the storage instance and initializes
		_adaptor.initializeStorage();
		
		// get BundleData instances for all inital bundles
		// the adaptor decides how the bundles are retireve
		// the default is MXML declarations of BundleData in BeanProviders
		_bundleDatas = _adaptor.getInstalledBundles(); // of IBundleData
		
		if (_bundleDatas != null)
		{
			// sort the datas by startLevel to determine startup order
			_bundleDatas.sortOn("startLevel", Array.NUMERIC);
			
			var bundle:IBundle;
			
			var i:int;
			var len:int = _bundleDatas.length;
			
			// create and add bundles to service
			for (i = 0; i < len; i++)
			{
				bundle = createBundle(_bundleDatas[i], _swiz, false);
			}
		}
		
		logger.info("BundleService initialized; All initial IBundle's created and loaded.");
		
		dispatchEvent(new BundleServiceEvent(
			BundleServiceEvent.BUNDLE_SERVICE_INITIALIZED));
	}
	
	/**
	 * @private
	 */
	protected function createAdaptor():IBundleServiceAdaptor
	{
		return new BundleServiceAdaptor();
	}
	
	/**
	 * @private
	 */
	protected function createBundle(data:BundleData, 
									swiz:ISwiz, 
									doStart:Boolean):IBundle
	{
		// create the bundle proxy
		var bundle:Bundle = Bundle.createBundle(data, this, swiz) as Bundle;

		// load the bundles activator
		bundle.load(0);
		
		// if do start, start the bundle
		if (doStart)
			Bundle(bundle).start(0);
		
		// add the bundle after is has been started
		addBundle(bundle);
		
		return bundle;
	}
	
	/**
	 * @private
	 */
	protected function startBundles():void
	{
		if (!_bundleDatas)
			return;
		
		var len:int = _bundleDatas.length;
		
		// start all bundles
		for (var i:int = 0; i < len; i++)
		{	
			Bundle(_collection[i]).start(0); 
		}
		
		setActive(true);
		
		logger.info("BundleService started; All initial IBundle's started.");
		
		dispatchEvent(new BundleServiceEvent(
			BundleServiceEvent.BUNDLE_SERVICE_STARTED));
	}
	
	//--------------------------------------------------------------------------
	//
	//  Protected :: Handlers
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	protected function addModule(module:IModuleInfo):void
	{
		pendingModules[module] = module;
		
		module.addEventListener(ModuleEvent.SETUP, module_setupHandler);
		module.addEventListener(ModuleEvent.READY, module_readyHandler);
		module.addEventListener(ModuleEvent.PROGRESS, module_progressHandler);
		module.addEventListener(ModuleEvent.ERROR, module_errorHandler);
	}
	
	/**
	 * @private
	 */
	protected function removeModule(module:IModuleInfo):void
	{
		delete pendingModules[module];
		
		module.removeEventListener(ModuleEvent.SETUP, module_setupHandler);
		module.removeEventListener(ModuleEvent.READY, module_readyHandler);
		module.removeEventListener(ModuleEvent.PROGRESS, module_progressHandler);
		module.removeEventListener(ModuleEvent.ERROR, module_errorHandler);
	}
	
	/**
	 * @private
	 */
	protected function module_setupHandler(event:ModuleEvent):void
	{
		var e:BundleServiceEvent = new BundleServiceEvent(
			BundleServiceEvent.BUNDLE_LOAD_SETUP);
		e.moduleEvent = event;
		
		dispatchEvent(e);
	}
	
	/**
	 * @private
	 */
	protected function module_progressHandler(event:ModuleEvent):void
	{
		var e:BundleServiceEvent = new BundleServiceEvent(
			BundleServiceEvent.BUNDLE_LOAD_PROGRESS);
		e.moduleEvent = event;
		
		dispatchEvent(e);
	}
	
	/**
	 * @private
	 */
	protected function module_readyHandler(event:ModuleEvent):void
	{
		removeModule(event.module);
		
		var instance:ISwizBundle = event.module.factory.create() as ISwizBundle;
		
		var moduleSwiz:ISwiz = instance.swiz;
		moduleSwiz.parentSwiz = swiz;
		moduleSwiz.init();
		
		var factory:IBeanFactory = moduleSwiz.beanFactory;
		
		var beans:Array = factory.beans;
		var data:BundleData;
		var bundle:IBundle;
		
		var len:int = beans.length;
		for (var i:int = 0; i < len; i++)
		{
			data = beans[i].source as BundleData;
			if (data)
			{
				bundle = createBundle(data, moduleSwiz, true);
				break; //only one
			}
		}
		
		var e:BundleServiceEvent = new BundleServiceEvent(
			BundleServiceEvent.BUNDLE_LOAD_COMPLETE, bundle);
		e.moduleEvent = event;
		
		dispatchEvent(e);
	}
	
	/**
	 * @private
	 */
	protected function module_errorHandler(event:ModuleEvent):void
	{
		removeModule(event.module);
		
		logger.error("loadBundle() failed, {0}", event.errorText);
		
		var e:BundleServiceEvent = new BundleServiceEvent(
			BundleServiceEvent.BUNDLE_LOAD_ERROR);
		e.moduleEvent = event;
		
		dispatchEvent(e);
	}
	
	/**
	 * @private
	 */
	private function instanceReady(token:IExecutableRequest, type:Class):void
	{
		var request:ExecutableRequest = token as ExecutableRequest;
		var uid:String = "";
		
		if (token.descriptor)
			uid = token.descriptor.uid;
		
		request.setType(type);
		
		if (token.autoCreate)
			request.setInstance(new type());
		
		if (token.isBean)
			beanService.setUpBean(token.instance, uid);
		
		request.apply();
	}
}
}